package de.unikiel.jmeter;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import net.sourceforge.jeval.EvaluationException;
import net.sourceforge.jeval.Evaluator;

import org.apache.jmeter.gui.util.VerticalPanel;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.timers.gui.AbstractTimerGui;

public class FunctionTimerGui extends AbstractTimerGui {

	private JTextField function;
	private PlotPanel plotPanel;
	private JButton plotButton;
	private JButton yscalePlusButton;
	private JButton yscaleMinusButton;
	private JButton xscalePlusButton;
	private JButton xscaleMinusButton;
	/**
	 * 
	 */
	private static final long serialVersionUID = 7088307519042125471L;

	public FunctionTimerGui() {
		init();
	}

	private JPanel makeParameterPanel() {
		final VerticalPanel p = new VerticalPanel();
		p.add(getParameterPanel());
		p.add(getPlotPanel());
		plotButton = new JButton("Plot");
		plotButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(final ActionEvent e) {
				final String text = function.getText();
				if (text != null && !text.matches("^\\s*$")) {
					plotPanel.plot(text);
				}
			}
		});
		final JPanel buttons = new JPanel();
		final JPanel xbuttons = new VerticalPanel();
		final JPanel ybuttons = new VerticalPanel();

		yscalePlusButton = new JButton("y zoom +");
		yscalePlusButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(final ActionEvent e) {
				plotPanel.setYscale(plotPanel.getYscale() * 3 / 2);
			}
		});
		ybuttons.add(yscalePlusButton);
		yscaleMinusButton = new JButton("y zoom -");
		yscaleMinusButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(final ActionEvent e) {
				plotPanel.setYscale(plotPanel.getYscale() * 2 / 3);
			}
		});
		ybuttons.add(yscaleMinusButton);

		xscalePlusButton = new JButton("x zoom +");
		xscalePlusButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(final ActionEvent e) {
				plotPanel.setXscale(plotPanel.getXscale() * 3 / 2);
			}
		});
		xbuttons.add(xscalePlusButton);
		xscaleMinusButton = new JButton("x zoom -");
		xscaleMinusButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(final ActionEvent e) {
				plotPanel.setXscale(plotPanel.getXscale() * 2 / 3);
			}
		});
		xbuttons.add(xscaleMinusButton);
		buttons.setLayout(new BorderLayout());
		buttons.add(plotButton, BorderLayout.NORTH);
		buttons.add(xbuttons, BorderLayout.WEST);
		buttons.add(ybuttons, BorderLayout.EAST);
		p.add(buttons);
		return p;
	}

	private void init() {
		setLayout(new BorderLayout());
		setBorder(makeBorder());
		final Box box = Box.createVerticalBox();
		box.add(makeTitlePanel());
		add(box, BorderLayout.NORTH);
		add(makeParameterPanel(), BorderLayout.CENTER);

	}

	@Override
	public String getStaticLabel() {
		return "Function Timer";
	}

	@Override
	public TestElement createTestElement() {
		final FunctionTimer functionTimer = new FunctionTimer();
		modifyTestElement(functionTimer);
		return functionTimer;
	}

	@Override
	public String getLabelResource() {
		return "Function Timer";
	}

	@Override
	public void modifyTestElement(final TestElement arg0) {
		super.configureTestElement(arg0);
		if (arg0 instanceof FunctionTimer) {
			final FunctionTimer t = (FunctionTimer) arg0;
			t.setProperty("expression", function.getText());
		}
	}

	@Override
	public void configure(final TestElement element) {
		super.configure(element);
		if (element instanceof FunctionTimer) {
			final FunctionTimer t = (FunctionTimer) element;
			function.setText(t.getPropertyAsString("expression"));
		}
	}

	private JPanel getParameterPanel() {
		final JPanel p = new JPanel();
		p.setLayout(new BorderLayout());
		p.add(new JLabel("Formula"), BorderLayout.WEST);
		p.add(function = new JTextField(50), BorderLayout.CENTER);
		return p;
	}

	private JPanel getPlotPanel() {
		plotPanel = new PlotPanel();
		return plotPanel;
	}

	class PlotPanel extends JPanel {

		private final BufferedImage img = new BufferedImage(640, 480,
				BufferedImage.TYPE_INT_RGB);
		private final Evaluator evaluator = new Evaluator();

		private double xscale = 1, yscale = 1;

		public PlotPanel() {
			final Graphics2D g = img.createGraphics();
			g.setBackground(Color.WHITE);
			g.clearRect(0, 0, 640, 480);
			setPreferredSize(new Dimension(700, 500));
		}

		@Override
		protected void paintComponent(final Graphics g) {
			g.drawImage(img, 0, 0, null);
		}

		public boolean plot(final String expression) {
			final Graphics2D g = (Graphics2D) img.getGraphics();
			g.setBackground(Color.WHITE);
			g.clearRect(0, 0, 640, 480);
			g.setColor(Color.BLACK);
			g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			evaluator.putVariable("t", 0 + "");
			double last = 0;
			System.out.println("x scale: " + xscale + " yscale: " + yscale);
			try {
				last = evaluator.getNumberResult(expression) * yscale;
			} catch (final EvaluationException e) {
				e.printStackTrace();
				return false;
			}
			double next;
			last = last < 0 ? 0 : last;
			for (int i = 1; i < 640; i++) {
				evaluator.putVariable("t", (i / xscale) * 1000 + "");
				try {
					next = evaluator.getNumberResult(expression) * yscale;
				} catch (final EvaluationException e) {
					e.printStackTrace();
					return false;
				}
				g.drawLine(i - 1, 480 - (int) (last + .5), i,
						480 - (int) (next + .5));
				last = next;
			}
			repaint();
			return true;
		}

		/**
		 * @return the xscale
		 */
		public final double getXscale() {
			return xscale;
		}

		/**
		 * @param xscale
		 *            the xscale to set
		 */
		public final void setXscale(final double xscale) {
			this.xscale = xscale;
		}

		/**
		 * @return the yscale
		 */
		public final double getYscale() {
			return yscale;
		}

		/**
		 * @param yscale
		 *            the yscale to set
		 */
		public final void setYscale(final double yscale) {
			this.yscale = yscale;
		}
	}
}
